﻿using Pvf.Core.Converters;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace Pvf.Core
{
    internal static class PvfObjectBuilder
    {
        //Code core
        public static Func<object, object> CreateGetValueDelegate(Type declareType, string propertyName)
        {
            // (object instance) => (object)((declaringType)instance).propertyName

            var paramInstance = Expression.Parameter(typeof(object));
            var bodyObjToType = Expression.Convert(paramInstance, declareType);
            var bodyGetTypeProperty = Expression.Property(bodyObjToType, propertyName);
            var bodyReturn = Expression.Convert(bodyGetTypeProperty, typeof(object));
            return Expression.Lambda<Func<object, object>>(bodyReturn, paramInstance).Compile();
        }

        public static Action<object, object> CreateSetValueDelegate(PropertyInfo property)
        {
            if (property == null || property.DeclaringType == null)
            {
                return null;
            }

            //Parameters required to declare methods
            var paramInstance = Expression.Parameter(typeof(object));
            var paramValue = Expression.Parameter(typeof(object));

            var bodyInstance = Expression.Convert(paramInstance, property.DeclaringType);
            var bodyValue = Expression.Convert(paramValue, property.PropertyType);
            var bodyCall = Expression.Call(bodyInstance, property.GetSetMethod(true), bodyValue);

            return Expression.Lambda<Action<object, object>>(bodyCall, paramInstance, paramValue).Compile();
        }

        // returns property getter
        public static Func<TObject, TProperty> GetPropGetter<TObject, TProperty>(string propertyName)
        {
            ParameterExpression paramExpression = Expression.Parameter(typeof(TObject), "value");

            Expression propertyGetterExpression = Expression.Property(paramExpression, propertyName);

            Func<TObject, TProperty> result =
                Expression.Lambda<Func<TObject, TProperty>>(propertyGetterExpression, paramExpression).Compile();

            return result;
        }

        // returns property setter:
        public static Action<TObject, TProperty> GetPropSetter<TObject, TProperty>(string propertyName)
        {
            ParameterExpression paramExpression = Expression.Parameter(typeof(TObject));

            ParameterExpression paramExpression2 = Expression.Parameter(typeof(TProperty), propertyName);

            MemberExpression propertyGetterExpression = Expression.Property(paramExpression, propertyName);

            Action<TObject, TProperty> result = Expression.Lambda<Action<TObject, TProperty>>
            (
                Expression.Assign(propertyGetterExpression, paramExpression2), paramExpression, paramExpression2
            ).Compile();

            return result;
        }

        private static bool IsStruct(Type source)
        {
            return source.IsValueType && !source.IsPrimitive && !source.IsEnum;
        }

        private static readonly Dictionary<Type, IPvfConverter> Registered = new Dictionary<Type, IPvfConverter>();

        private static readonly Dictionary<Type, Type> TypeConverterMappings = new Dictionary<Type, Type>();

        private static IPvfConverter BuildPrivate(Type objType)
        {
            var cConverterAttr = objType.GetCustomAttribute<PvfCustomConverterAttribute>();

            if (cConverterAttr != null && cConverterAttr.InType != null)
            {
                if (Activator.CreateInstance(cConverterAttr.InType) is IPvfConverter converter)
                {
                    return converter;
                }
            }

            if (objType.IsEnum)
            {
                var gType = typeof(PvfEnumConverter<>).MakeGenericType(objType);

                return Activator.CreateInstance(gType) as IPvfConverter;
            }

            var typeCode = Type.GetTypeCode(objType);
            switch (typeCode)
            {
                case TypeCode.Empty:
                    break;
                case TypeCode.Object:
                    {
                        if (objType.IsGenericType)
                        {
                            var gTypeDef = objType.GetGenericTypeDefinition();
                            if (typeof(Dictionary<,>) == gTypeDef)
                            {
                                var tType = typeof(PvfDictConverter<,>).MakeGenericType(objType.GetGenericArguments()[0], objType.GetGenericArguments()[1]);
                                return Activator.CreateInstance(tType) as IPvfConverter;
                            }
                            else if (typeof(List<>) == gTypeDef)
                            {
                                var tType = typeof(PvfListConverter<>).MakeGenericType(objType.GetGenericArguments()[0]);
                                return Activator.CreateInstance(tType) as IPvfConverter;
                            }
                        }
                        else
                        {
                            if (objType == typeof(PvfIndexRef))
                            {
                                return new PvfIndexRefConverter();
                            }
                        }
                        Type eType = null;
                        if (TypeConverterMappings.TryGetValue(objType, out var foundConverterType))
                        {
                            eType = foundConverterType;
                        }

                        if (eType == null)
                        {
                            eType = typeof(PvfObjectConverter<>).MakeGenericType(objType);
                        }
                        return Activator.CreateInstance(eType) as IPvfConverter;
                    }
                case TypeCode.DBNull:
                    break;
                case TypeCode.Boolean:
                    {
                        return new PvfBooleanConverter();
                    }
                case TypeCode.Char:
                    {

                    }
                    break;
                case TypeCode.SByte:
                    {

                    }
                    break;
                case TypeCode.Byte:
                    {

                    }
                    break;
                case TypeCode.Int16:
                    {
                        return new PvfInt32Converter();
                    }
                case TypeCode.UInt16:
                    {
                        return new PvfInt32Converter();
                    }
                case TypeCode.Int32:
                    {
                        return new PvfInt32Converter();
                    }
                case TypeCode.UInt32:
                    {
                        return new PvfInt32Converter();
                    }
                case TypeCode.Int64:
                    {
                        return new PvfInt32Converter();
                    }
                case TypeCode.UInt64:
                    {
                        return new PvfInt32Converter();
                    }
                case TypeCode.Single:
                    {
                        return new PvfSingleConverter();
                    }
                case TypeCode.Double:
                    {
                        return new PvfSingleConverter();
                    }
                case TypeCode.Decimal:
                    {
                        return new PvfSingleConverter();
                    }
                case TypeCode.DateTime:
                    {

                    }
                    break;
                case TypeCode.String:
                    {
                        return new PvfStringConverter();
                    }
                default:
                    break;
            }
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="objType"></param>
        /// <param name="env"></param>
        /// <returns></returns>
        public static IPvfConverter Build(Type objType, IPvfEnv env, IPvfObjectLayout layout = null)
        {
            if (Registered.TryGetValue(objType, out var pvfConverter))
            {
                return pvfConverter;
            }

            if (env is null)
            {
                throw new ArgumentNullException(nameof(env));
            }

            var converter = BuildPrivate(objType);

            if (converter != null)
            {
                converter.Init(layout ?? new PvfObjectLayout(objType, env));
            }

            Registered.Add(objType, converter);

            return converter;
        }
    }
}
